🎖️GitЯра🎖️
Commit 3c76fc972ce69f31558b81c539e51f58808ed8da
Parents : 4c09a3f
Author : Jeremiah K <17190268+jeremiah-k@users.noreply.github.com>
Signature : Signature validation error
Date : 2026-07-26T19:48:23-05:00
Committer : GitHub <noreply@github.com>
Date : 2026-07-26T19:48:23-05:00
fix(map): configure OSMdroid user agent at F-Droid startup (#6448)
Changes
13 files changed, 114 insertions(+), 37 deletions(-)
Diff
diff --git a/androidApp/src/fdroid/kotlin/org/meshtastic/app/FlavorApplicationConfiguration.kt b/androidApp/src/fdroid/kotlin/org/meshtastic/app/FlavorApplicationConfiguration.kt
new file mode 100644
index 0000000000..f6ed9d6757
--- /dev/null
+++ b/androidApp/src/fdroid/kotlin/org/meshtastic/app/FlavorApplicationConfiguration.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2026 Meshtastic LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package org.meshtastic.app
+
+import org.osmdroid.config.Configuration
+
+/** Configures F-Droid-only process globals before any OSMdroid map or tile provider can be created. */
+internal fun configureFlavorApplication(applicationId: String) {
+ Configuration.getInstance().userAgentValue = applicationId
+}
diff --git a/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/MapView.kt b/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/MapView.kt
index 4445944aca..f1e769ce0a 100644
--- a/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/MapView.kt
+++ b/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/MapView.kt
@@ -315,7 +315,6 @@ fun MapView(
val initialCameraPosition = (initialCameraState as InitialCameraState.Ready).position
val map =
rememberMapViewWithLifecycle(
- applicationId = mapViewModel.applicationId,
zoomLevel = initialCameraPosition?.zoom ?: INITIAL_MAP_ZOOM,
mapCenter = initialCameraPosition?.let { GeoPoint(it.latitude, it.longitude) } ?: GeoPoint(0.0, 0.0),
tileSource = loadOnlineTileSourceBase(),
diff --git a/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt b/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt
index b42b9aa6f8..403068e3af 100644
--- a/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt
+++ b/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt
@@ -25,7 +25,6 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import org.koin.core.annotation.KoinViewModel
-import org.meshtastic.core.common.BuildConfigProvider
import org.meshtastic.core.model.Node
import org.meshtastic.core.repository.MapCameraPosition
import org.meshtastic.core.repository.MapPrefs
@@ -47,7 +46,6 @@ class MapViewModel(
radioController: RadioController,
radioConfigRepository: RadioConfigRepository,
notificationPrefs: NotificationPrefs,
- buildConfigProvider: BuildConfigProvider,
private val mapLayersManager: MapLayersManager,
savedStateHandle: SavedStateHandle,
) : BaseMapViewModel(
@@ -87,8 +85,6 @@ class MapViewModel(
mapPrefs.setMapStyle(value)
}
- val applicationId = buildConfigProvider.applicationId
-
/** Imported overlay layers; owned by the flavor-neutral [MapLayersManager] and drawn on the OSMdroid map. */
val mapLayers: StateFlow<List<MapLayerItem>> = mapLayersManager.mapLayers
diff --git a/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/MapViewWithLifecycle.kt b/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/MapViewWithLifecycle.kt
index 912f5fe189..d4df30178e 100644
--- a/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/MapViewWithLifecycle.kt
+++ b/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/MapViewWithLifecycle.kt
@@ -29,7 +29,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
-import org.osmdroid.config.Configuration
import org.osmdroid.tileprovider.tilesource.ITileSource
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
import org.osmdroid.util.BoundingBox
@@ -44,7 +43,6 @@ private const val DEFAULT_ZOOM_LEVEL = 15.0
@Suppress("MagicNumber")
@Composable
fun rememberMapViewWithLifecycle(
- applicationId: String,
box: BoundingBox,
tileSource: ITileSource = TileSourceFactory.DEFAULT_TILE_SOURCE,
): MapView {
@@ -55,18 +53,12 @@ fun rememberMapViewWithLifecycle(
DEFAULT_ZOOM_LEVEL
}
val center = GeoPoint(box.centerLatitude, box.centerLongitude)
- return rememberMapViewWithLifecycle(
- applicationId = applicationId,
- zoomLevel = zoom,
- mapCenter = center,
- tileSource = tileSource,
- )
+ return rememberMapViewWithLifecycle(zoomLevel = zoom, mapCenter = center, tileSource = tileSource)
}
@Suppress("LongMethod")
@Composable
internal fun rememberMapViewWithLifecycle(
- applicationId: String,
zoomLevel: Double = MIN_ZOOM_LEVEL,
mapCenter: GeoPoint = GeoPoint(0.0, 0.0),
tileSource: ITileSource = TileSourceFactory.DEFAULT_TILE_SOURCE,
@@ -88,8 +80,6 @@ internal fun rememberMapViewWithLifecycle(
MapView(context).apply {
clipToOutline = true
- // Required to get online tiles
- Configuration.getInstance().userAgentValue = applicationId
setTileSource(tileSource)
isVerticalMapRepetitionEnabled = false // disables map repetition
setMultiTouchControls(true)
diff --git a/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/discovery/DiscoveryOsmMap.kt b/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/discovery/DiscoveryOsmMap.kt
index 8b1692bc1c..731e54fa2f 100644
--- a/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/discovery/DiscoveryOsmMap.kt
+++ b/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/discovery/DiscoveryOsmMap.kt
@@ -79,12 +79,7 @@ fun DiscoveryOsmMap(
var hasCentered by remember { mutableStateOf(false) }
- val mapView =
- rememberMapViewWithLifecycle(
- applicationId = context.packageName,
- box = initialBounds,
- tileSource = CustomTileSource.getTileSource(0),
- )
+ val mapView = rememberMapViewWithLifecycle(box = initialBounds, tileSource = CustomTileSource.getTileSource(0))
// Camera auto-center once
LaunchedEffect(allGeoPoints) {
diff --git a/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/node/NodeMapScreen.kt b/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/node/NodeMapScreen.kt
index 949655f292..0c568e0487 100644
--- a/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/node/NodeMapScreen.kt
+++ b/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/node/NodeMapScreen.kt
@@ -46,7 +46,6 @@ fun NodeMapScreen(nodeMapViewModel: NodeMapViewModel, onNavigateUp: () -> Unit)
) { paddingValues ->
NodeTrackOsmMap(
positions = positions,
- applicationId = nodeMapViewModel.applicationId,
mapStyleId = nodeMapViewModel.mapStyleId,
modifier = Modifier.fillMaxSize().padding(paddingValues),
)
diff --git a/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/node/NodeTrackMap.kt b/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/node/NodeTrackMap.kt
index 77b595d88e..6cf65e1bc7 100644
--- a/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/node/NodeTrackMap.kt
+++ b/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/node/NodeTrackMap.kt
@@ -24,8 +24,7 @@ import org.meshtastic.proto.Position
/**
* Flavor-unified entry point for the embeddable node-track map. Resolves [destNum] to obtain
- * [NodeMapViewModel.applicationId] and [NodeMapViewModel.mapStyleId], then delegates to the OSMDroid implementation
- * ([NodeTrackOsmMap]).
+ * [NodeMapViewModel.mapStyleId], then delegates to the OSMDroid implementation ([NodeTrackOsmMap]).
*
* Supports optional synchronized selection via [selectedPositionTime] and [onPositionSelected].
*/
@@ -41,7 +40,6 @@ fun NodeTrackMap(
vm.setDestNum(destNum)
NodeTrackOsmMap(
positions = positions,
- applicationId = vm.applicationId,
mapStyleId = vm.mapStyleId,
modifier = modifier,
selectedPositionTime = selectedPositionTime,
diff --git a/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/node/NodeTrackOsmMap.kt b/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/node/NodeTrackOsmMap.kt
index a6aec4c2dc..c2498126c7 100644
--- a/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/node/NodeTrackOsmMap.kt
+++ b/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/node/NodeTrackOsmMap.kt
@@ -72,7 +72,6 @@ import kotlin.math.roundToInt
@Composable
fun NodeTrackOsmMap(
positions: List<Position>,
- applicationId: String,
mapStyleId: Int,
modifier: Modifier = Modifier,
selectedPositionTime: Int? = null,
@@ -96,11 +95,7 @@ fun NodeTrackOsmMap(
}
val cameraView = remember(geoPoints) { BoundingBox.fromGeoPoints(geoPoints) }
val mapView =
- rememberMapViewWithLifecycle(
- applicationId = applicationId,
- box = cameraView,
- tileSource = CustomTileSource.getTileSource(mapStyleId),
- )
+ rememberMapViewWithLifecycle(box = cameraView, tileSource = CustomTileSource.getTileSource(mapStyleId))
var filterMenuExpanded by remember { mutableStateOf(false) }
diff --git a/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/traceroute/TracerouteOsmMap.kt b/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/traceroute/TracerouteOsmMap.kt
index 2563a86267..3d3530e3a7 100644
--- a/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/traceroute/TracerouteOsmMap.kt
+++ b/androidApp/src/fdroid/kotlin/org/meshtastic/app/map/traceroute/TracerouteOsmMap.kt
@@ -161,7 +161,6 @@ fun TracerouteOsmMap(
val mapView =
rememberMapViewWithLifecycle(
- applicationId = mapViewModel.applicationId,
box = initialCameraView ?: BoundingBox(),
tileSource = CustomTileSource.getTileSource(mapViewModel.mapStyleId),
)
diff --git a/androidApp/src/google/kotlin/org/meshtastic/app/FlavorApplicationConfiguration.kt b/androidApp/src/google/kotlin/org/meshtastic/app/FlavorApplicationConfiguration.kt
new file mode 100644
index 0000000000..debbf12014
--- /dev/null
+++ b/androidApp/src/google/kotlin/org/meshtastic/app/FlavorApplicationConfiguration.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2026 Meshtastic LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package org.meshtastic.app
+
+/** Google builds have no flavor-specific application globals to configure. */
+internal fun configureFlavorApplication(@Suppress("UNUSED_PARAMETER") applicationId: String) = Unit
diff --git a/androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt b/androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt
index e5d7009d52..bb1375a165 100644
--- a/androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt
+++ b/androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt
@@ -76,6 +76,7 @@ open class MeshUtilApplication :
override fun onCreate() {
super.onCreate()
ContextServices.app = this
+ configureFlavorApplication(BuildConfig.APPLICATION_ID)
startKoin<AndroidKoinApp> {
androidContext(this@MeshUtilApplication)
diff --git a/androidApp/src/testFdroid/kotlin/org/meshtastic/app/FlavorApplicationConfigurationTest.kt b/androidApp/src/testFdroid/kotlin/org/meshtastic/app/FlavorApplicationConfigurationTest.kt
new file mode 100644
index 0000000000..1ff9bb2288
--- /dev/null
+++ b/androidApp/src/testFdroid/kotlin/org/meshtastic/app/FlavorApplicationConfigurationTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2026 Meshtastic LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package org.meshtastic.app
+
+import androidx.test.core.app.ApplicationProvider
+import org.junit.runner.RunWith
+import org.osmdroid.config.Configuration
+import org.osmdroid.views.MapView
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class FlavorApplicationConfigurationTest {
+ @Test
+ fun `configureFlavorApplication helper sets osmdroid user agent`() {
+ val configuration = Configuration.getInstance()
+ val original = configuration.userAgentValue
+ try {
+ configureFlavorApplication("test.user.agent")
+ assertEquals("test.user.agent", configuration.userAgentValue)
+ } finally {
+ configuration.userAgentValue = original
+ }
+ }
+}
+
+@RunWith(RobolectricTestRunner::class)
+@Config(application = MeshUtilApplication::class, sdk = [34])
+class FlavorApplicationStartupTest {
+ @Test
+ fun `production startup configures osmdroid before first map creation`() {
+ val configuration = Configuration.getInstance()
+ val original = configuration.userAgentValue
+ try {
+ val application = ApplicationProvider.getApplicationContext<MeshUtilApplication>()
+
+ assertEquals(application.packageName, BuildConfig.APPLICATION_ID)
+ assertEquals(BuildConfig.APPLICATION_ID, configuration.userAgentValue)
+ MapView(application).let { mapView ->
+ try {
+ assertEquals(BuildConfig.APPLICATION_ID, configuration.userAgentValue)
+ } finally {
+ mapView.onDetach()
+ }
+ }
+ } finally {
+ configuration.userAgentValue = original
+ }
+ }
+}
diff --git a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/node/NodeMapViewModel.kt b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/node/NodeMapViewModel.kt
index 9390913099..6204dc5ecf 100644
--- a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/node/NodeMapViewModel.kt
+++ b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/node/NodeMapViewModel.kt
@@ -28,7 +28,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.toList
import org.koin.core.annotation.KoinViewModel
-import org.meshtastic.core.common.BuildConfigProvider
import org.meshtastic.core.model.MeshLog
import org.meshtastic.core.repository.MapPrefs
import org.meshtastic.core.repository.MeshLogRepository
@@ -43,7 +42,6 @@ class NodeMapViewModel(
savedStateHandle: SavedStateHandle,
nodeRepository: NodeRepository,
meshLogRepository: MeshLogRepository,
- buildConfigProvider: BuildConfigProvider,
private val mapPrefs: MapPrefs,
) : ViewModel() {
private val destNumFromRoute = savedStateHandle.get<Int>("destNum")
@@ -62,8 +60,6 @@ class NodeMapViewModel(
.distinctUntilChanged()
.stateInWhileSubscribed(initialValue = null)
- val applicationId = buildConfigProvider.applicationId
-
private val ourNodeNumFlow = nodeRepository.myNodeInfo.map { it?.myNodeNum }.distinctUntilChanged()
val positionLogs: StateFlow<List<Position>> =
Served by rngit 1.5.0 - Generated in 0.19s